/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.data;

import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import com.google.common.base.Function;

/**
 * A collection of utility methods that deal with {@code Either} objects.
 *
 * @author Oliver Horn
 */
public final class Eithers {

	private Eithers() {
	}

	/**
	 * Constructs a {@code Either.Left} object with the given value.
	 * @param value the value
	 * @see Either
	 */
	public static <L, R> Either.Left<L, R> left(@Nullable L value) {
		return new Either.Left<L, R>(value);
	}

	/**
	 * Constructs a {@code Either.Right} object with the given value.
	 * @param value the value
	 * @see Either
	 */
	public static <L, R> Either.Right<L, R> right(@Nullable R value) {
		return new Either.Right<L, R>(value);
	}

	/**
	 * Tries to parse the input string as a signed decimal {@code byte}.
	 * This method calls {@link Byte#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Byte> parseByte(String input) {
		try {
			return right(Byte.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed {@code byte} in the specified radix.
	 * This method calls {@link Byte#valueOf(String, int)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Byte> parseByte(String input, int radix) {
		try {
			return right(Byte.valueOf(input, radix));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed decimal {@code short}.
	 * This method calls {@link Short#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Short> parseShort(String input) {
		try {
			return right(Short.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed {@code short} in the specified radix.
	 * This method calls {@link Short#valueOf(String, int)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Short> parseShort(String input, int radix) {
		try {
			return right(Short.valueOf(input, radix));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed decimal {@code int}.
	 * This method calls {@link Integer#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Integer> parseInt(String input) {
		try {
			return right(Integer.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed {@code int} in the specified radix.
	 * This method calls {@link Integer#valueOf(String, int)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Integer> parseInt(String input, int radix) {
		try {
			return right(Integer.valueOf(input, radix));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed decimal {@code long}.
	 * This method calls {@link Long#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Long> parseLong(String input) {
		try {
			return right(Long.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a signed {@code long} in the specified radix.
	 * This method calls {@link Long#valueOf(String, int)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Long> parseLong(String input, int radix) {
		try {
			return right(Long.valueOf(input, radix));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a {@code float}.
	 * This method calls {@link Float#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Float> parseFloat(String input) {
		try {
			return right(Float.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to parse the input string as a {@code double}.
	 * This method calls {@link Double#valueOf(String)}.
	 * If successful, the parsed value is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * This method never throws a {@code NumberFormatException}.
	 * @param input the input string
	 */
	public static Either<String, Double> parseDouble(String input) {
		try {
			return right(Double.valueOf(input));
		} catch (NumberFormatException e) {
			return left(input);
		}
	}

	/**
	 * Tries to match the input string against the given regular expression.
	 * If successful, the matched string is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * @param input the input string
	 * @see #parsePattern(String, Pattern)
	 */
	public static Either<String, String> parsePattern(String input, String regex) {
		return parsePattern(input, Pattern.compile(regex));
	}

	/**
	 * Tries to match the input string against the given pattern.
	 * If successful, the matched string is returned as {@link Either.Right},
	 * otherwise the input string is returned as {@link Either.Left}.
	 * @param input the input string
	 */
	public static Either<String, String> parsePattern(String input, Pattern pattern) {
		Matcher matcher = pattern.matcher(input);
		if (matcher.matches()) {
			return right(matcher.group());
		} else {
			return left(input);
		}
	}

	public static <T> Either<Exception, T> tryCallable(Callable<T> callable) {
		try {
			return right(callable.call());
		} catch (Exception e) {
			return left(e);
		}
	}

	public static <F, T> Either<Exception, T> tryFunction(Function<F, T> function, F arg) {
		try {
			return right(function.apply(arg));
		} catch (Exception e) {
			return left(e);
		}
	}

	/**
	 * Extracts the {@code Right} value, or rethrows the {@code Left} exception.
	 */
	public static <E extends Throwable, T> T extract(Either<E, T> either) throws E {
		if (either.isRight()) {
			return either.right();
		} else {
			throw either.left();
		}
	}

	/**
	 * Extracts the {@code Right} value, or rethrows the {@code Left} exception.
	 * <p>
	 * Note that this method does not declare any throws clause.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T> T unsafeExtract(Either<? extends Throwable, T> either) {
		// We pretend that the exception object is an unchecked exception.
		// This satisfies the compiler, and due to type erasure, no additional
		// cast is generated in the bytecode.
		return (T) Eithers.<RuntimeException, T>extract((Either) either);
	}
}
